Programas/atividades desenvolvidas para a disciplina DCA0445 - Processamento Digital de Imagens, do curso de Engenharia de Computação da Universidade Federal do Rio grande do Norte UFRN

Prefácio

Todos os programas, neste documento, foram desenvolvidas em C++ utilizando-se da biblioteca OpenCV e em ambiente Linux. Para compilar qualquer programa presente neste documento, pode-se fazer uso deste Makefile, coloca o Makefile na mesma pasta do código fonte, extensão .cpp, e execute via terminal o comando make <nome_do_programa>. Todos os códigos encontram-se no Repositório do github.

1. Programa Region

Este programa consiste em negativar uma certa região dentro de uma imagem, delimitada por um retângulo informado pelo usuário. o programa varre a área correspondente na imagem e troca os valores dos pixels para seus inversos, ou seja 255 - valor_atual.

Compilando e Executando.

$ make region
$ ./region <caminho_para_a_imagem>

O código fonte completo se encontra aqui region.cpp.

void region(Mat &img, CvPoint *p){
  for(unsigned int i = p[0].x; i < p[1].x; i++)
    for(unsigned int j = p[0].y; j < p[1].y; j++)
      img.at<uint8_t>(i,j) = 255 - img.at<uint8_t>(i,j);
}
toto mini
Figura 1. Entrada do programa Region
region result
Figura 2. Saída do programa Region

2. Troca Regiões

O usuário deve passar uma imagem qualquer, e o programa passara para escala de cinza e particionara a imagem em 4(quatro) partes simétricas e realizara a troca na diagonal dessas quatro subimagens.

Para este programa uma imagem foi pensada sendo composta por 4 regiões da seguinte forma:

A

B

C

D

Compilando e Executando.

$ make trocaregioes
$ ./trocaregioes <caminho_para_a_imagem>

Código completo em trocaregioes.cpp

{
  w = img.size().width;
  h = img.size().height;
  result = img.clone();

  img(cv::Rect(0,0, w/2, h/2)).copyTo(result(cv::Rect((w-1)/2, (h-1)/2, w/2, h/2)));  (1)
  img(cv::Rect((w-1)/2, 0, w/2, h/2)).copyTo(result(cv::Rect(0, (h-1)/2, w/2, h/2))); (2)
  img(cv::Rect(0, (h-1)/2, w/2, h/2)).copyTo(result(cv::Rect((w-1)/2, 0, w/2, h/2))); (3)
  img(cv::Rect((w-1)/2, (h-1)/2, w/2, h/2)).copyTo(result(cv::Rect(0, 0, w/2, h/2))); (4)
}
1 Sobrepoe A da img original em D de result
2 Sobrepoe B da img original em C de result
3 Sobrepoe C da img original em B de result
4 Sobrepoe D da img original em A de result

Resultado

D

C

B

A

toto mini
Figura 3. Entrada do programa trocaregioes
trocaRegiao resultado
Figura 4. Saída do programa trocaregioes

3. Conta Bolhas

Este programa consiste em contar o número de regiões brancas puras, com e sem "buracos", o fundo da imagem deve ser puramente preto e os objetos puramente brancos, o programa foi testado utilizando a imagem bolhas.png. Mas o mesmo deve funcionar para qualquer imagem que siga o padrão especificado a cima.

bolhas
Figura 5. Bolhas.png

O algoritmo consiste em 4 passos bem definidos. O código completo se encontra neste link: contaregioes.cpp.

3.1. Passo 1- Remover objetos das bordas

//remove da borda superior e inferior
for(int i = 0; i < width; i++){
  if(image.at<uint8_t>(0, i) == OBJ_COLOR)
    floodFill(image, CvPoint(i, 0), BACK_COLOR);
  if(image.at<uint8_t>(height-1,i) == OBJ_COLOR)
    floodFill(image, CvPoint(i, height-1), BACK_COLOR);
}

//remove das laterais
for(int i = 0; i < height; i++){
  //lateral esquerda
  if(image.at<uint8_t>(i, 0) == OBJ_COLOR)
    floodFill(image, CvPoint(0, i), BACK_COLOR);
  //lateral direita
  if(image.at<uint8_t>(i, width-1) == OBJ_COLOR)
    floodFill(image, CvPoint(width-1, i), BACK_COLOR);
}

3.2. Passo 2- Contar bolhas com buraco

//troca o background, para facilitar a identificar os buracos das bolhas
floodFill(image, CvPoint(0,0), NEW_BACK_COLOR);
for(int i = 0; i < height; i++)
  for(int j = 0; j < width; j++)
  {
    //identifica uma bolha com buraco
    if(image.at<uint8_t>(i,j) == BACK_COLOR && image.at<uint8_t>(i,j-1) == OBJ_COLOR){
      //soma um no numero de bolhas e "apaga" a bolha encontrada
      nbolhas_com_buracos++;
      floodFill(image, CvPoint(j-1, i), NEW_BACK_COLOR);
    }
  }

3.3. Passo 3- Contar bolhas sem buracos

//conta bolhas sem buracos
for(int i = 0; i < height; i++)
  for(int j = 0; j < width; j++)
  {
    //identifica uma bola
    if(image.at<uint8_t>(i,j) == OBJ_COLOR){
      //soma um no numero de bolhas e "apaga" a bolha encontrada
      nbolhas_sem_buracos++;
      floodFill(image, CvPoint(j, i), NEW_BACK_COLOR);
    }
  }

3.4. Resultado

conta bolhas resultado
Figura 6. Resultado da contagem.

4. Histograma

4.1. Equalize

Implementação de um Equalizador de Histograma para imagens em tons de cinza.

Algoritmo de equalização (imagens em tons de cinza) utilizado:

  1. Calcular Histograma: \(h(r_k), k \in [0,255\)];

  2. Calcular Histograma Acumulado: \(ha(r_k) = \sum{h(r_j)}, j \in [0,255\)];

  3. Normalizar o Histograma Acumulado, na faixa de [0, 255]: \(ha(r_k) = ha(r_k)/ha(r_255)\);

  4. Transformar a imagem: \(f(x,y) = ha(f(x,y))\).

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char** argv){
  Mat frame;
  Mat hist;
  VideoCapture cap;
  uint8_t histEq[256];

  int histsize = 256;
  int sum;
  float range[] = {0, 256};
  const float *histrange = { range };

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis\n";
    return -1;
  }

  std::cout << "Pressione qualquer tecla para encerrar o programa." << '\n';
  while(1){
    cap >> frame;

    cvtColor(frame, frame, CV_BGR2GRAY);
    imshow("Original", frame);
    //Calculo do histograma
    calcHist(&frame, 1, 0, Mat(), hist, 1, &histsize, &histrange);


    /*calculo do histograma acumulado */
    /*e normalizacao do histograma acumulado*/
    /**
     * Calcula o vetor que ira realizar a transformacao nos valores dos pixels
     */
    sum = 0;
    for(int i = 0; i < histsize; i++)
    {
      sum+= hist.at<float>(i);
      histEq[i] = sum*255.0/frame.total();
    }

    //substituicao dos valores dos pixels
    for(int i = 0; i < frame.size().height; i++)
      for(int j = 0; j < frame.size().width; j++)
        frame.at<uint8_t>(i,j) = histEq[frame.at<uint8_t>(i,j)];
    imshow("Equalizado", frame);

    if(waitKey(10) != 255)break;
  }

  return 0;
}

Antes e Depois da equalização do histograma.

equalize input
Figura 7. Entrada do programa equalize
equalize output
Figura 8. Resultado do programa equalize

Código completo: equalize.cpp

4.2. Motiondetector

Utilizando comparação de histogramas entre frames consecutivos, comparando-o por calculo da correlação(usando função do OpenCV, compareHist), para identificar ocorrência de movimento, para isso foi estabelecido, de forma empírica, um limiar para a correlação, ao se identificar um valor de correlação abaixo do limiar, um circulo verde é desenhado no canto superior direito da imagem, indicando uma detecção de movimento.

Makefile utilizado para compilar o programa motiondetector, é diferente pois inclui a capacidade de gerar gifs.

Motiondetector
Figura 9. Resultado do Motiondetector.
#include <iostream>
#include <opencv2/opencv.hpp>

#include <GraphicsMagick/Magick++.h>

using namespace cv;
using namespace std;

#define NUM_FRAMES_GIF 7
#define GIF_DELAY 100 //ms

int main(int argc, char** argv){
  Magick::InitializeMagick(NULL);
  vector<Magick::Image> gifFrames(NUM_FRAMES_GIF);

  Mat frame, grayFrame;
  Mat H1, H2;
  VideoCapture cap;
  int i = 0;
  double r_correl = 0;

  int histsize = 256;
  float range[] = {0, 256};
  const float *histrange = { range };

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis";
    return -1;
  }

  cap >> frame; //captura um frame
  cvtColor(frame, frame, CV_BGR2GRAY); //converte para escala de cinza
  calcHist(&frame, 1, 0, Mat(), H1, 1, &histsize, &histrange);

  while(true){
    H2 = H1.clone();
    cap >> frame; //captura um frame
    cvtColor(frame, grayFrame, CV_BGR2GRAY); //converte para escala de cinza
    calcHist(&grayFrame, 1, 0, Mat(), H1, 1, &histsize, &histrange);

    r_correl =  compareHist(H1, H2, CV_COMP_CORREL);

    if(r_correl <= 0.95)//movimento
    {
      circle(frame, Point(frame.cols - 20, 20), 10, Scalar(0, 255, 0), CV_FILLED);
    }else{
      circle(frame, Point(frame.cols - 20, 20), 10, Scalar(255, 255 ,255), CV_FILLED);
    }

    imshow("Live", frame);

    //Salva o frame para o array de frames que sera usado para gerar o gif
    gifFrames[i] = Magick::Image(frame.cols,
                                 frame.rows, "BGR",
                                 Magick::StorageType::CharPixel,
                                (uint8_t*)frame.data);
    gifFrames[i].animationDelay(GIF_DELAY);
    i = (i+1)%NUM_FRAMES_GIF; //para simular uma fila circular com o vector

    if(waitKey(30) != 255)break;
  }

  //Gera o gif, colocando-o no arquivo "saidaKmeans.gif"
  Magick::writeImages(gifFrames.begin(), gifFrames.end(), "Motiondetector.gif");
  return 0;
}

Download do código fonte: Motiondetector.cpp.

5. Filtros 2D

5.1. Laplaciano do Gaussiano (Lapgauss)

Filter2D
Figura 10. Resultado do lapgauss.cpp

código completo: lapgauss.cpp

#include <iostream>
#include <opencv2/opencv.hpp>
#include <GraphicsMagick/Magick++.h>

using namespace cv;
using namespace std;

#define MAX_FILTER 2

#define NUM_FRAMES_GIF 100
#define GIF_DELAY 10 //ms

void printmask(Mat &m){
  for(int i=0; i<m.size().height; i++){
    for(int j=0; j<m.size().width; j++){
      cout << m.at<float>(i,j) << ",";
    }
    cout << endl;
  }
}

void menu(){
  cout << "\npressione a tecla para ativar o filtro: \n"
	         "a - calcular modulo\n"
           "m - media\n"
           "g - gauss\n"
           "v - vertical\n"
	         "h - horizontal\n"
           "l - laplaciano\n"
           "i - identidade\n"
           "e - laplaciano do Gaussiano\n"
	         "esc - sair\n";
}

int main(int argvc, char** argv){
  Magick::InitializeMagick(NULL);
  vector<Magick::Image> gifFrames(NUM_FRAMES_GIF);
  int k = 0;

  VideoCapture video;
  float identidade[] = {0, 0, 0,
                        0, 1, 0,
                        0, 0, 0};
  float media[] = {1,1,1,
				           1,1,1,
				           1,1,1};
  float gauss[] = {1,2,1,
				           2,4,2,
				           1,2,1};
  float horizontal[]={-1,0,1,
					            -2,0,2,
					            -1,0,1};
  float vertical[]={-1,-2,-1,
					           0, 0, 0,
					           1, 2, 1};
  float laplacian[]={0,-1,0,
					          -1,4,-1,
					           0,-1,0};

  Mat cap, frame, frame32f, frameFiltered;
  Mat mask[] = { Mat(3,3, CV_32F), Mat(3,3, CV_32F) };
  Mat maskAux;
  Mat result;
  char* text = "Media";
  double width, height;
  int absolut;
  char key;

  video.open(0);
  if(!video.isOpened())
    return -1;
  width=video.get(CV_CAP_PROP_FRAME_WIDTH);
  height=video.get(CV_CAP_PROP_FRAME_HEIGHT);
  std::cout << "largura=" << width << "\n";;
  std::cout << "altura =" << height<< "\n";;

  namedWindow("filtroespacial",1);

  mask[0] = Mat(3, 3, CV_32F, media);
  scaleAdd(mask[0], 1/9.0, Mat::zeros(3,3,CV_32F), maskAux);
  mask[0] = maskAux.clone();
  absolut = 1; // calcs abs of the image
  mask[1] = Mat(3, 3, CV_32F, identidade);

  menu();
  for(;;){
    video >> cap;
    cvtColor(cap, frame, CV_BGR2GRAY);//converte a imagem para tons de cinza
    flip(frame, frame, 1); //espelha a imagem

    imshow("original", frame);//exibe a imagem

    frame.convertTo(frameFiltered, CV_32F); //cria uma imagem tipo float
    //aplicacao dos filtros, seguindo uma ordem, da mascara[0] ate mascara[i]
    //fazendo com que os efeitos das filtragens sejam cascateados
    for(int i = 0; i < MAX_FILTER; i++)
    {
      filter2D(frameFiltered, frameFiltered, frameFiltered.depth(), mask[i], Point(1,1), 0); //aplica o filtro 1
      if(absolut)frameFiltered = abs(frameFiltered);
    }

    frameFiltered.convertTo(result, CV_8U); //converte a imagem filtrada de float para byte em tons de cinza
    putText(result, text, Point(5,50), FONT_HERSHEY_DUPLEX, 1, Scalar(255), 2);

    imshow("filtroespacial", result);//exibe a imagem filtrada


    key = (char) waitKey(10);
    if( key == 27 ) break; // esc pressed!
    switch(key){
    case 'a':
	  menu();
      absolut=!absolut;
      break;
    case 'm':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, media);
      scaleAdd(mask[0], 1/9.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Media";



      break;
    case 'g':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, gauss);
      scaleAdd(mask[0], 1/16.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Gaussiano";
      break;
    case 'h':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, horizontal);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Horizontal";
      break;
    case 'v':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, vertical);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Vertical";
      break;
    case 'l':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, laplacian);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Laplaciano";
      break;
    case 'i':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, identidade);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Identidade";
      break;
    case 'e':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, gauss);
      scaleAdd(mask[0], 1/16.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, laplacian);
      text = "Laplaciano do Gaussiano";
      break;
    default:
      break;
    }


    for(int i = 0; i < height; i++)
      for(int j = 0; j < width; j++)
        result.at<uint8_t>(i,j) = 255 - result.at<uint8_t>(i,j);

    gifFrames[k] = Magick::Image(result.cols,
                                 result.rows, "K",
                                 Magick::StorageType::CharPixel,
                                (uint8_t*)result.data);

    gifFrames[k].animationDelay(GIF_DELAY);
    k = (k+1)%NUM_FRAMES_GIF; //para simular uma fila circular com o vector
  }
  //Gera o gif, colocando-o no arquivo "saidaKmeans.gif"
  Magick::writeImages(gifFrames.begin(), gifFrames.end(), "Filter2D.gif");
  return 0;
}

5.2. Tilt-Shift

Demonstração Tiltshift

Código completo: tiltshift.cpp.

#include <iostream>
#include <math.h>       /* tanh, log */
#include <opencv2/opencv.hpp>

using namespace cv;

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring);

void slot_sliders(int, void*);
double alfa_func(int x, int l1, int l2, double d);

Mat image, blender;
int decay_int = 50;
int center_int = 50;
int c_width_int = 50;
int blurring_int = 50;
const char mainWindow[] = "TiltSfhit";


int main(int argc, char** argv){
  const int max_slider = 100;

  if(argc != 1){//usuario passou um argumento, vou entender como sendo um endereco de uma imagem
    image = imread(argv[1]);
  }else{
    image = imread("imagens/toto_bola_cores.png");
  }

  if(image.data == NULL){
    std::cerr << "Erro ao abrir a Imagem!!" << '\n';
    return 1;
  }

  namedWindow(mainWindow, WINDOW_AUTOSIZE);
  imshow(mainWindow, image);

  createTrackbar( "Central Width [0,100]", mainWindow,
                  &c_width_int,
                  max_slider,
                  slot_sliders);

  createTrackbar( "Center [0,100]", "TiltSfhit",
        				  &center_int,
        				  max_slider,
        				  slot_sliders);

  createTrackbar( "Decay [0,100]", mainWindow,
        				  &decay_int,
        				  max_slider,
        				  slot_sliders);

  createTrackbar( "Blurring [0,100]", mainWindow,
                  &blurring_int,
                  max_slider,
                  slot_sliders);

  slot_sliders(0,0);

  waitKey(0);
  return 0;
}

void slot_sliders(int, void*){
  tiltShift(image, blender, c_width_int/100.0, decay_int/100.0, center_int/100.0, blurring_int/100.0);
  imshow(mainWindow, blender);
};

double alfa_func(int x, int l1, int l2, double d){
  return 0.5*( tanh((x-l1)/d) - tanh((x-l2)/d) );
}

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring)
{
  if(delta_l > 1 || center > 1 || decay > 1 || blurring > 1)return true;
  if(delta_l < 0 || center < 0 || decay < 0 || blurring < 0)return true;
  if(image_src.data == NULL)return true;

  const static int intensity_max = 100;
  Mat blurry_img;
  const int x_max = image_src.rows;
  Mat mask, image_32f;
  float weigth = 1/18.0;
  float average[] = {2, 2, 2, 2, 2, 2, 2, 2, 2};


  mask = Mat(3, 3, CV_32F, average);
  scaleAdd(mask, weigth, Mat::zeros(3,3,CV_32F), mask);
  image_src.convertTo(image_32f, CV_32F);
  for(int i = 0; i < intensity_max*blurring; i++)
  {
    filter2D(image_32f, image_32f, image_32f.depth(), mask, Point(1,1));
    image_32f = abs(image_32f);
  }
  image_32f.convertTo(blurry_img, CV_8U);

  #define L1 (center - delta_l/2.0)*x_max
  #define L2 (center + delta_l/2.0)*x_max
  image_dest = image_src.clone();
  double alfa;
  for(int x = 0; x < x_max; x++)
  {
      alfa = alfa_func(x, L1, L2, decay);
      addWeighted(  image_src.row(x),
                    alfa,
                    blurry_img.row(x),
                    1-alfa,
                    0.0,
                    image_dest.row(x));
  }

  return false;
};

5.3. Tilt-Shift em Vídeo

Demonstração Tiltshift em vídeo

Código completo em: tiltshift_video.cpp.

#include <iostream>
#include <math.h>       /* tanh, log */
#include <opencv2/opencv.hpp>

using namespace cv;

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring);//0 to 1

int main(int argc, char** argv){
  VideoCapture video_src;
  Mat frame;

  video_src.open(argv[1]);
  if(video_src.isOpened() == false){
    std::cerr << "Erro ao carregar arquivo de video!" << '\n';
    return 1;
  }

  int frame_width = static_cast<int>(video_src.get(CAP_PROP_FRAME_WIDTH));
  int frame_height = static_cast<int>(video_src.get(CAP_PROP_FRAME_HEIGHT));
  Size frame_size(frame_width, frame_height);
  int frames_per_second = 3;
  int steps = 10;

  //Create and initialize the VideoWriter object
  VideoWriter video_result("resultados/tiltshift_video.avi", VideoWriter::fourcc('X','V','I','D'),
                                                             frames_per_second, frame_size, true);
  if(video_result.isOpened() == false){
   std::cerr << "Erro ao carregar arquivo de video!" << '\n';
   return 1;
  }

  std::cout << "Processando..." << '\n';

  double alpha = 1.6;
  int beta = 1;

  while(true){
    video_src >> frame;
    if(frame.data == NULL)break;

    tiltShift(frame, frame, 0.3, 0.0001, 0.4, 0.15);

    for( int y = 0; y < frame.rows; y++ ) {
        for( int x = 0; x < frame.cols; x++ ) {
            for( int c = 0; c < 3; c++ ) {
                frame.at<Vec3b>(y,x)[c] =
                saturate_cast<uchar>( alpha*( frame.at<Vec3b>(y,x)[c] ) + beta );
            }
        }
    }

    video_result.write(frame);

    int i;
    for(i = 0; i < steps; i++)
    {
      if(frame.data == NULL)break; //final do arquivo
      video_src >> frame;
    }
    if(i != steps)break;
  }

  video_src.release();
  video_result.release();

  std::cout << "Terminou!" << '\n';

  system("vlc resultados/tiltshift_video.avi");

  return 0;
}


double alfa_func(int x, int l1, int l2, double d){
  return 0.5*( tanh((x-l1)/d) - tanh((x-l2)/d) );
}

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring)
{
  if(delta_l > 1 || center > 1 || decay > 1 || blurring > 1)return true;
  if(delta_l < 0 || center < 0 || decay < 0 || blurring < 0)return true;
  if(image_src.data == NULL)return true;

  const static int intensity_max = 100;
  Mat blurry_img;
  const int x_max = image_src.rows;
  Mat mask, image_32f;
  float weigth = 1/9.0;
  float average[] = {1, 1, 1, 1, 1, 1, 1, 1, 1};


  mask = Mat(3, 3, CV_32F, average);
  scaleAdd(mask, weigth, Mat::zeros(3,3,CV_32F), mask);
  image_src.convertTo(image_32f, CV_32F);
  for(int i = 0; i < intensity_max*blurring; i++)
  {
    filter2D(image_32f, image_32f, image_32f.depth(), mask, Point(1,1));
    image_32f = abs(image_32f);
  }
  image_32f.convertTo(blurry_img, CV_8U);

  #define L1 (center - delta_l/2.0)*x_max
  #define L2 (center + delta_l/2.0)*x_max
  image_dest = image_src.clone();
  double alfa;
  for(int x = 0; x < x_max; x++)
  {
      alfa = alfa_func(x, L1, L2, decay);
      addWeighted(  image_src.row(x),
                    alfa,
                    blurry_img.row(x),
                    1-alfa,
                    0.0,
                    image_dest.row(x));
  }

  return false;
};

6. Filtro Homomórfico

Falta implementar…​

7. Bibliografia

  • Rafael Gonzalez. 'Processamento Digital de Imagens'. Addison-Wesley. 1990. 2 ed.